#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "LOGC.h"

static int nnbridge( char *nnbridge_ip , int accept_nrbridge_port , int accept_user_port )
{
	int			nrbridge_listen_sock ;
	struct sockaddr_in	nrbridge_listen_addr ;
	int			nrbridge_accept_sock ;
	struct sockaddr_in	nrbridge_accept_addr ;
	int			user_listen_sock ;
	struct sockaddr_in	user_listen_addr ;
	int			user_accept_sock ;
	struct sockaddr_in	user_accept_addr ;
	socklen_t		addr_len = sizeof(struct sockaddr_in) ;
	int			on ;
	
	fd_set			fds ;
	int			max_fd ;
	
	int			readlen ;
	char			buffer[ 4096 + 1 ] ;
	int			writelen ;
	
	int			nret = 0 ;
	
	nrbridge_listen_sock = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP );
	if( nrbridge_listen_sock == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "socket failed[%d]errno[%d]" , nrbridge_listen_sock , errno );
		return -1;
	}
	
	on = 1 ;
	setsockopt( nrbridge_listen_sock , SOL_SOCKET , SO_REUSEADDR , (void *) & on, sizeof(on) );
	
	memset( & nrbridge_listen_addr , 0x00 , sizeof(struct sockaddr_in) );
	nrbridge_listen_addr.sin_family = AF_INET ;
	nrbridge_listen_addr.sin_addr.s_addr = inet_addr( nnbridge_ip ) ;
	nrbridge_listen_addr.sin_port = htons( (unsigned short)accept_nrbridge_port );
	
	nret = bind( nrbridge_listen_sock , (struct sockaddr *) & nrbridge_listen_addr , sizeof(struct sockaddr) ) ;
	if( nret == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "bind[%d] failed[%d]errno[%d]" , nrbridge_listen_sock , nret , errno );
		return -1;
	}
	
	nret = listen( nrbridge_listen_sock , 1024 ) ;
	if( nret == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "listen[%d] failed[%d]errno[%d]" , nrbridge_listen_sock , nret , errno );
		return -1;
	}
	else
	{
		InfoLog( __FILE__ , __LINE__ , "listen nrbridge ok" );
	}
	
	user_listen_sock = socket( AF_INET , SOCK_STREAM , IPPROTO_TCP );
	if( user_listen_sock == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "socket failed[%d]errno[%d]" , user_listen_sock , errno );
		return -1;
	}
	
	on = 1 ;
	setsockopt( user_listen_sock , SOL_SOCKET , SO_REUSEADDR , (void *) & on, sizeof(on) );
	
	memset( & user_listen_addr , 0x00 , sizeof(struct sockaddr_in) );
	user_listen_addr.sin_family = AF_INET ;
	user_listen_addr.sin_addr.s_addr = inet_addr( nnbridge_ip ) ;
	user_listen_addr.sin_port = htons( (unsigned short)accept_user_port );
	
	nret = bind( user_listen_sock , (struct sockaddr *) & user_listen_addr , sizeof(struct sockaddr) ) ;
	if( nret == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "bind[%d] failed[%d]errno[%d]" , user_listen_sock , nret , errno );
		return -1;
	}
	
	nret = listen( user_listen_sock , 1024 ) ;
	if( nret == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "listen[%d] failed[%d]errno[%d]" , user_listen_sock , nret , errno );
		return -1;
	}
	else
	{
		InfoLog( __FILE__ , __LINE__ , "listen user ok" );
	}
	
	nrbridge_accept_sock = accept( nrbridge_listen_sock , (struct sockaddr *) & nrbridge_accept_addr , & addr_len ) ;
	if( nrbridge_accept_sock == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "accept[%d] failed[%d]errno[%d]" , nrbridge_listen_sock , nrbridge_accept_sock  , errno );
		return -1;
	}
	else
	{
		InfoLog( __FILE__ , __LINE__ , "accept nrbridge ok" );
	}
	
	user_accept_sock = accept( user_listen_sock , (struct sockaddr *) & user_accept_addr , & addr_len ) ;
	if( user_accept_sock == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "accept[%d] failed[%d]errno[%d]" , user_listen_sock , user_accept_sock  , errno );
		return -1;
	}
	else
	{
		InfoLog( __FILE__ , __LINE__ , "accept user ok" );
	}
	
	memset( buffer , 0x00 , sizeof(buffer) );
	readlen = 1 ;
	writelen = write( nrbridge_accept_sock , buffer , readlen ) ;
	if( writelen == -1 )
	{
		ErrorLog( __FILE__ , __LINE__ , "write nrbridge_accept_sock failed[%d]errno[%d]" , writelen , errno );
		return -1;
	}
	else if( writelen != readlen )
	{
		ErrorLog( __FILE__ , __LINE__ , "writelen[%d]bytes nrbridge_accept_sock not match with readlen[%d]bytes" , writelen , readlen );
		return -1;
	}
	else
	{
		InfoLog( __FILE__ , __LINE__ , "nnbridge -> nrbridge_accept_sock [%d]bytes" , writelen );
	}
	
	while(1)
	{
		FD_ZERO( & fds );
		FD_SET( nrbridge_accept_sock , & fds );
		FD_SET( user_accept_sock , & fds );
		if( nrbridge_accept_sock > user_accept_sock )
			max_fd = nrbridge_accept_sock ;
		else
			max_fd = user_accept_sock ;
		
		nret = select( max_fd + 1 , & fds , NULL , NULL , NULL ) ;
		if( nret == -1 )
		{
			ErrorLog( __FILE__ , __LINE__ , "select failed[%d]errno[%d]" , nret , errno );
			return -1;
		}
		
		if( FD_ISSET( nrbridge_accept_sock , & fds ) )
		{
			memset( buffer , 0x00 , sizeof(buffer) );
			readlen = (int)read( nrbridge_accept_sock , buffer , sizeof(buffer)-1 ) ;
			if( readlen == -1 )
			{
				ErrorLog( __FILE__ , __LINE__ , "read nrbridge_accept_sock failed[%d]errno[%d]" , readlen , errno );
				return -1;
			}
			else if( readlen == 0 )
			{
				InfoLog( __FILE__ , __LINE__ , "nrbridge_accept_sock socket close" );
				break;
			}
			
			writelen = write( user_accept_sock , buffer , readlen ) ;
			if( writelen == -1 )
			{
				ErrorLog( __FILE__ , __LINE__ , "write user_accept_sock failed[%d]errno[%d]" , writelen , errno );
				return -1;
			}
			else if( writelen != readlen )
			{
				ErrorLog( __FILE__ , __LINE__ , "writelen[%d]bytes user_accept_sock not match with readlen[%d]bytes" , writelen , readlen );
				break;
			}
			else
			{
				InfoLog( __FILE__ , __LINE__ , "nrbridge_accept_sock -> user_accept_sock [%d]bytes" , writelen );
			}
		}
		
		if( FD_ISSET( user_accept_sock , & fds ) )
		{
			memset( buffer , 0x00 , sizeof(buffer) );
			readlen = (int)read( user_accept_sock , buffer , sizeof(buffer)-1 ) ;
			if( readlen == -1 )
			{
				ErrorLog( __FILE__ , __LINE__ , "read user_accept_sock failed[%d]errno[%d]" , readlen , errno );
				return -1;
			}
			else if( readlen == 0 )
			{
				InfoLog( __FILE__ , __LINE__ , "user_accept_sock socket close" );
				break;
			}
			
			writelen = write( nrbridge_accept_sock , buffer , readlen ) ;
			if( writelen == -1 )
			{
				ErrorLog( __FILE__ , __LINE__ , "write nrbridge_accept_sock failed[%d]errno[%d]" , writelen , errno );
				return -1;
			}
			else if( writelen != readlen )
			{
				ErrorLog( __FILE__ , __LINE__ , "writelen[%d]bytes nrbridge_accept_sock not match with readlen[%d]bytes" , writelen , readlen );
				break;
			}
			else
			{
				InfoLog( __FILE__ , __LINE__ , "user_accept_sock -> nrbridge_accept_sock [%d]bytes" , writelen );
			}
		}
	}
	
	close( nrbridge_accept_sock );
	close( user_accept_sock );
	
	return 0;
}

static void usage()
{
	printf( "nnbridge v0.1.0\n" );
	printf( "USAGE : nnbridge nnbridge_ip accept_nrbridge_port accept_user_port\n" );
	return;
}

int main( int argc , char *argv[] )
{
	if( argc == 1 + 3 )
	{
		// SetLogFile( "%s/log/nnbridge.log" , getenv("HOME") );
		SetLogLevel( LOGLEVEL_DEBUG );
		return -nnbridge( argv[1] , atoi(argv[2]) , atoi(argv[3]) );
	}
	else
	{
		usage();
		return 9;
	}
}
